<template>
  <div>
    <tg-control-bar
      v-if="hasControlBar"
      class="control-bar"
      :left-items="controlBarLeftItems"
      :right-items="controlBarRightItems"
    >
      <template slot="right">
        <tg-form-basic-popover
          ref="ref-searcher"
          :style="{'padding-left': '10px'}"
          :title="searcherTitle"
          :max-height="searcherMaxHeight"
          :hold-height="searcherHoldHeight"
          :form="searcher"
          :control-bar="searcherControlBar"
          :popover-c-m="searcherPopoverCM"
          :reference-c-m="searcherReferenceCM"
        />
        <el-divider direction="vertical" />
        <el-button-package :c-m="refreshButtonCM" />
        <tg-button-status
          :active.sync="checkStrictly"
          :button-c-m="checkStrictlyButtonCM"
        />
      </template>
    </tg-control-bar>
    <el-scrollbar
      :style="scrollbarStyle"
      class="scroll-container"
    >
      <el-tree-package
        ref="ref-tree"
        class="tree"
        :c-m="treeCM"
        :loading="treeLoading"
      />
    </el-scrollbar>
  </div>
</template>

<script lang="ts">
/*
prop 列表
  maxHeight: number                                                               表单最大高度 [默认为 -1，既未指定，各子类按实现需要处理默认值]
  holdHeight: boolean                                                             保持表单高度 [默认为 true]
  tree: {
    treeProps?: Map<string, any>                                               [参考 ComponentModel.props]
    treeEvents?: Map<string, Function | Function[]>                            [参考 ComponentModel.events]
    node-key: string                                                              节点键
    props?: TreeProps                                                             节点属性 [参考 ElTree.props]
    load?: (node: TreeNode<any, any>, params: any) => Promise<AxiosResponse<any>> 远程加载表格 data
    menu?: ComponentModel                                                         节点右键菜单 [参考 ElDropdown]
  }
  controlBar!: {
    leftItems?: ComponentModel[]                                                  [参考 TgControlBar.@Prop.leftItems]
    rightItems?: ComponentModel[]                                                 [参考 TgControlBar.@Prop.rightItems]
    showSearcher?: boolean                                                        是否显示搜索器按钮 [默认为 true]
  }
controlBar.[leftItems | rightItems].[i].meta 扩展
  checkedNodeVisible: boolean                                                     是否选中数据后激活
ref 列表
  ref-tree [默认]
  ref-searcher
*/
import { Component, Prop } from 'vue-property-decorator'
import { AxiosResponse } from 'axios'
import { Tree } from 'element-ui'
import { TreeProps, TreeNode } from 'element-ui/types/tree'
import { ComponentModel } from '@TG/interface'
import { IElTreePackage } from '@TG/interface/types/component'
import { emptyResponse, validResponse } from '@TG/kit/request'
import { isEmpty, isNotEmpty } from '@TG/kit/validate'
import { appModule } from '@TG/store/modules/app'
import TgComponent from '../TgComponent'
import ElButtonPackage from '../ElPackage/ElButtonPackage.vue'
import ElTreePackage from '../ElPackage/ElTreePackage.vue'
import TgButtonStatus from '../TgButton/TgButtonStatus.vue'
import TgControlBar from '../TgControlBar/index.vue'
import TgFormBasicPopover from '../TgForm/TgFormBasicPopover.vue'

@Component({
  name: 'TgTreeBasic',
  components: {
    ElButtonPackage,
    ElTreePackage,
    TgButtonStatus,
    TgControlBar,
    TgFormBasicPopover
  }
})
export default class extends TgComponent {
  @Prop({ type: Number, default: -1 }) private maxHeight!: number
  @Prop({ type: Boolean, default: true }) private holdHeight!: boolean
  @Prop({ type: Object, required: true }) private tree!: {
    treeProps?: Map<string, any>
    treeEvents?: Map<string, Function | Function[]>
    'node-key': string
    props?: TreeProps
    load?: (node: TreeNode<any, any>, params: any) => Promise<AxiosResponse<any>>
    menu?: ComponentModel
  }
  @Prop({ type: Object }) private controlBar!: {
    leftItems?: ComponentModel[]
    rightItems?: ComponentModel[]
    showSearcher?: boolean
  }

  private loadData: boolean = false
  private frozenParams: any = {}
  private treeLoading: boolean = false
  private searcherInitable = true
  private searcherTitle: string = '搜索器'
  private searcherMaxHeight: number = 120
  private searcherHoldHeight: boolean = true
  private searcher = { handleFormItems: this.handleSearcherItems }
  private searcherControlBar = { showSave: false, showReset: false }
  private searcherPopoverCM = {
    props: { width: 300 },
    events: {
      show: () => {
        if (this.searcherInitable) {
          const searcherRef = this.ref('searcher') as TgFormBasicPopover
          if (isNotEmpty(searcherRef)) {
            searcherRef.initForm('view')
            this.searcherInitable = false
          }
        }
      }
    }
  }
  private queryString: string = ''
  private refreshButtonCM: ComponentModel = {
    props: new Map<string, any>([
      ['plain', false],
      ['icon', 'el-icon-antd-retweet']
    ]),
    events: new Map<string, Function | Function[]>([
      ['click', () => this.refresh()]
    ]),
    meta: { tip: '刷新选中的节点或整棵树' }
  }
  private checkStrictly: boolean = true
  private checkStrictlyButtonCM: ComponentModel = {
    props: new Map<string, any>([
      ['icon', 'el-icon-antd-branches']
    ]),
    meta: { tip: '节点级联勾选' }
  }
  private checkedNodes: any[] = []

  get treeCM(): ComponentModel {
    const propsConfig: any = this.tree.props
    const props: Map<string, any> = new Map<string, any>([
      ['node-key', this.tree['node-key']],
      ['check-strictly', !this.checkStrictly],
      ['filter-node-method', (value: any, data: any, node: TreeNode<any, any>) => this.filterNodeMethod(value, data, node)],
      ['lazy', true],
      ['load', (node: TreeNode<any, any>, resolve: (data: any[]) => void) => this.load(node, resolve)]
    ])
    if (isNotEmpty(propsConfig)) {
      props.set('props', propsConfig as TreeProps)
    }
    Object.assign(props, this.tree.treeProps)
    const treeEvents = this.tree.treeEvents || {}
    const events: Map<string, Function | Function[]> = new Map<string, Function | Function[]>([
      ['check', (data: any, checked: { checkedNodes: any[], checkedKeys: any[], halfCheckedNodes: any[], halfCheckedKeys: any[] }) => this.check(data, checked)]
    ])
    Object.assign(events, this.tree.treeEvents)
    const menu = this.tree.menu
    const treeCM: ComponentModel = { props, events }
    if (isNotEmpty(menu)) {
      treeCM.meta = { menu }
    }
    return treeCM
  }

  get hasControlBar() {
    const showSearcher = this.controlBar?.showSearcher === undefined ? true : this.controlBar.showSearcher
    return showSearcher || this.controlBarLeftItems.length > 0 || this.controlBarRightItems.length > 0
  }

  get controlBarLeftItems() {
    const leftItems = this.controlBar?.leftItems || []
    const items: ComponentModel[] = []
    leftItems.forEach(item => {
      const checkedNodeVisible = item.meta?.checkedNodeVisible || false
      if (!checkedNodeVisible || isNotEmpty(this.checkedNodes)) {
        items.push(item)
      }
    })
    return items
  }

  get controlBarRightItems() {
    const items: ComponentModel[] = []
    const rightItems = this.controlBar?.rightItems || []
    rightItems.forEach(item => {
      const checkedNodeVisible = item.meta?.checkedNodeVisible || false
      if (!checkedNodeVisible || isNotEmpty(this.checkedNodes)) {
        items.push(item)
      }
    })
    return items
  }

  get searcherReferenceCM(): ComponentModel {
    /*
    检查是否有搜索条件
    检查到搜索条件后对按钮进行样式处理，提醒用户
    */
    let tip = '搜索器'
    let type = 'primary'
    if (isNotEmpty(this.queryString)) {
      tip = '点击查看已使用的搜索条件'
      type = 'success'
    }
    return {
      props: new Map<string, any>([
        ['type', type],
        ['icon', 'el-icon-antd-search']
      ]),
      meta: { tip }
    }
  }

  get scrollbarStyle() {
    let maxHeight: number = this.maxHeight === -1 ? appModule.pageSize.height : this.maxHeight
    if (isNotEmpty(this.hasControlBar)) {
      maxHeight -= 58 // searcher 58
      maxHeight -= 1 // searchermargin 1
    }
    if (this.holdHeight) {
      return { height: `${maxHeight}px` }
    } else {
      return { 'max-height': `${maxHeight}px` }
    }
  }

  private handleSearcherItems(operate: string, model: any) {
    const items: ComponentModel[] = []
    items.push({
      props: new Map<string, any>([
        ['prop', 'queryString'],
        ['style', { 'margin-bottom': '0px' }]
      ]),
      items: [
        {
          tag: 'el-input',
          name: 'queryString',
          items: [
            {
              tag: 'el-button',
              props: new Map<string, any>([
                ['slot', 'append'],
                ['class', 'el-icon-antd-search']
              ]),
              events: new Map<string, Function | Function[]>([
                ['click', () => this.filterNode()]
              ])
            }
          ]
        }
      ],
      meta: { span: 24 }
    })
    return items
  }

  private check(data: any, checked: { checkedNodes: any[], checkedKeys: any[], halfCheckedNodes: any[], halfCheckedKeys: any[] }) {
    this.checkedNodes = checked.checkedNodes
  }

  private load(node: TreeNode<any, any>, resolve: (data: any[]) => void) {
    if (this.loadData) {
      const load = this.tree.load || ((node: TreeNode<any, any>, params: any) => Promise.resolve(emptyResponse([])))
      const querier = this.getQuerier()
      load(node, querier).then(response => {
        if (validResponse(response)) {
          resolve(response.data)
        }
      })
    } else {
      this.loadData = true
    }
  }

  private filterNodeMethod(value: any, data: any, node: TreeNode<any, any>) {
    const label = node.label
    if (isEmpty(value) || isEmpty(label)) {
      return true
    }
    return label.indexOf(value) !== -1
  }

  public initTree(loadData: boolean = true, frozenParams?: any) {
    this.loadData = loadData
    if (isNotEmpty(frozenParams) && JSON.stringify(frozenParams) !== JSON.stringify(this.frozenParams)) {
      this.frozenParams = frozenParams
    }
    if (this.loadData) {
      this.refresh()
    }
  }

  public getQuerier() {
    return Object.assign({ queryString: this.queryString }, this.frozenParams)
  }

  public filterNode() {
    const searcherRef = this.ref('searcher') as TgFormBasicPopover
    if (isNotEmpty(searcherRef)) {
      const model = searcherRef.getModel()
      this.queryString = model.queryString
      const ref = this.ref() as IElTreePackage
      if (isNotEmpty(ref)) {
        ref.filterNode(this.queryString)
        // 手工同步 model
        searcherRef.handleFormInput(model)
      }
      searcherRef.hidePopover()
    }
  }

  public reloadNode(parent?: any) {
    const props = this.treeCM.props
    if (isNotEmpty(props)) {
      const lazy = props?.get('lazy')
      const load = props?.get('load')
      if (isNotEmpty(lazy) && isNotEmpty(load)) {
        const ref = this.ref() as IElTreePackage
        if (isNotEmpty(ref)) {
          ref.reloadNode(parent)
          this.checkedNodes = []
        }
      }
    }
  }

  private refresh() {
    const ref = this.ref() as ElTreePackage
    if (isNotEmpty(ref)) {
      const treeRef = ref.ref() as Tree
      if (isNotEmpty(treeRef)) {
        const currentKey = treeRef.getCurrentKey()
        if (isNotEmpty(currentKey)) {
          const currentNode = treeRef.getNode(currentKey)
          this.reloadNode(currentNode)
        } else {
          this.reloadNode()
        }
      }
    }
  }

  public defaultRef() {
    return 'tree'
  }
}
</script>

<style lang="scss" scoped>
::v-deep {
  .el-divider--vertical {
    margin-top: 10px;
  }
}

.control-bar {
  margin-bottom: 1px;
}

.scroll-container {
  background-color: #fff;
  border: 1px solid #dfe6ec;
  ::v-deep {
    .el-scrollbar__wrap {
      height: 100%;
      overflow-x: hidden;
    }
    .el-scrollbar__view {
      overflow-x: hidden;
    }
  }
  .tree {
    margin-top: 6px;
    margin-bottom: 23px;
  }
}
</style>
